package com.inifitness.tenant.core.config.mp;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import com.baomidou.mybatisplus.annotation.TableName;
import com.inifitness.common.annotations.History;
import com.inifitness.common.constants.GlobalConstants;

import lombok.RequiredArgsConstructor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

/**
 * TODO: (sunjinfeng 2024-05-24)
 *
 * @author sunjinfeng
 * @version 1.0.0
 * @since 2024-05-24 09:27
 */
@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
        Object.class})
})
@Component
@RequiredArgsConstructor
public class HistoryInterceptor implements Interceptor {

  private final DataSource dataSource;
  private static final String DELETE_SQL = "DELETE FROM ? WHERE id = ?";
  private static final String DDL_UPDATE_KEY = "update";
  private static final String DDL_DELETE_KEY = "delete";
  private static final String DDL_INSERT_KEY = "insert";

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    Object parameter = invocation.getArgs()[1];
    if (parameter != null && parameter.getClass().isAnnotationPresent(History.class)) {
      Class<?> entityClass = parameter.getClass();
      String tableName = entityClass.getAnnotation(TableName.class).value();
      String historyTableName = tableName + "_history";

      // 动态创建历史表
      createHistoryTableIfNotExists(entityClass, tableName, historyTableName);

      // 插入历史记录
      if (DDL_UPDATE_KEY.equalsIgnoreCase(invocation.getMethod().getName())) {
        // 更新操作时，将旧数据插入历史表，并删除原表中的旧数据
        insertHistoryRecord(entityClass, parameter, historyTableName, DDL_UPDATE_KEY);
        deleteOriginalRecord(entityClass, parameter, tableName);
      }
      if (!DDL_DELETE_KEY.equalsIgnoreCase(invocation.getMethod().getName())) {
        // 插入新记录
        insertHistoryRecord(entityClass, parameter, historyTableName, DDL_INSERT_KEY);
      }
    }
    return invocation.proceed();
  }

  private void createHistoryTableIfNotExists(@SuppressWarnings("unused") Class<?> entityClass, String tableName,
      String historyTableName) {
    try (Connection connection = dataSource.getConnection();
        ResultSet tables = connection.getMetaData().getTables(null, null, historyTableName, new String[]{"TABLE"})
    ) {
      if (!tables.next()) {
        String createTableSql = generateCreateTableSql(connection, tableName, historyTableName);
        try (PreparedStatement statement = connection.prepareStatement(createTableSql)) {
          statement.executeUpdate();
        }
      }
    } catch (SQLException e) {
      throw new RuntimeException("Failed to create history table", e);
    }
  }

  private String generateCreateTableSql(Connection connection, String tableName,
      String historyTableName) throws SQLException {
    StringBuilder sql = new StringBuilder("CREATE TABLE IF NOT EXISTS ").append(historyTableName)
        .append(" (");
    DatabaseMetaData metaData = connection.getMetaData();
    try(ResultSet columns = metaData.getColumns(null, null, tableName, null)) {
      boolean hasTenantId = false;
      while (columns.next()) {
        String columnName = columns.getString("COLUMN_NAME");
        if (hasTenantId || GlobalConstants.SYSTEM_TENANT_ID.equalsIgnoreCase(columnName)) {
          hasTenantId = true;
        }
        String columnType = columns.getString("TYPE_NAME");
        int columnSize = columns.getInt("COLUMN_SIZE");
        sql.append(columnName).append(" ").append(columnType);
        if ("VARCHAR".equalsIgnoreCase(columnType)) {
          sql.append("(").append(columnSize).append(")");
        }
        sql.append(",");
      }
      if (hasTenantId) {
        sql.append("operation_type VARCHAR(20)) PARTITION BY HASH (tenant_id) PARTITIONS 10;");
      } else {
        sql.append("operation_type VARCHAR(20));");
      }
    }
    return sql.toString();
  }

  private void insertHistoryRecord(Class<?> entityClass, Object entity, String historyTableName,
      String operationType) {
    try (Connection connection = dataSource.getConnection()) {
      String insertSql = generateInsertSql(entityClass, historyTableName);
      try (PreparedStatement statement = connection.prepareStatement(insertSql)) {
        int index = 0;
        for (Field field : entityClass.getDeclaredFields()) {
          if (Modifier.isStatic(field.getModifiers())) {
            continue;
          }
          ReflectionUtils.makeAccessible(field);
          Object value = field.get(entity);
          statement.setObject(++index, value);
        }
        statement.setObject(++index, operationType);
        statement.executeUpdate();
      }
    } catch (SQLException | IllegalAccessException e) {
      throw new RuntimeException("Failed to insert history record", e);
    }
  }

  private void deleteOriginalRecord(Class<?> entityClass, Object entity, String tableName) {
    try (Connection connection = dataSource.getConnection()) {
      try (PreparedStatement statement = connection.prepareStatement(DELETE_SQL)) {
        Field codeField = entityClass.getDeclaredField("id");
        ReflectionUtils.makeAccessible(codeField);
        Object codeValue = codeField.get(entity);
        statement.setObject(1, tableName);
        statement.setObject(2, codeValue);
        statement.executeUpdate();
      }
    } catch (SQLException | NoSuchFieldException | IllegalAccessException e) {
      throw new RuntimeException("Failed to delete original record", e);
    }
  }

  private String generateInsertSql(Class<?> entityClass, String historyTableName) {
    StringBuilder sql = new StringBuilder("INSERT INTO ").append(historyTableName).append(" (");
    for (Field field : entityClass.getDeclaredFields()) {
      if (Modifier.isStatic(field.getModifiers())) {
        continue;
      }
      String columnName = field.getName();
      sql.append(columnName).append(",");
    }
    sql.append("operation_type) VALUES (");
    for (int i = 0; i < entityClass.getDeclaredFields().length; i++) {
      if (!Modifier.isStatic(entityClass.getDeclaredFields()[i].getModifiers())) {
        sql.append("?,");
      }
    }
    sql.append("?)");
    return sql.toString();
  }
}
